home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / php / pear / PEAR / Installer.php < prev    next >
PHP Script  |  2004-10-01  |  40KB  |  1,049 lines

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 5                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 3.0 of the PHP license,       |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available through the world-wide-web at the following url:           |
  11. // | http://www.php.net/license/3_0.txt.                                  |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Stig Bakken <ssb@php.net>                                   |
  17. // |          Tomas V.V.Cox <cox@idecnet.com>                             |
  18. // |          Martin Jansen <mj@php.net>                                  |
  19. // +----------------------------------------------------------------------+
  20. //
  21. // $Id: Installer.php,v 1.148 2004/02/29 15:58:17 avsm Exp $
  22.  
  23. require_once 'PEAR/Common.php';
  24. require_once 'PEAR/Registry.php';
  25. require_once 'PEAR/Dependency.php';
  26. require_once 'PEAR/Downloader.php';
  27. require_once 'System.php';
  28.  
  29. define('PEAR_INSTALLER_OK',       1);
  30. define('PEAR_INSTALLER_FAILED',   0);
  31. define('PEAR_INSTALLER_SKIPPED', -1);
  32. define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
  33.  
  34. /**
  35.  * Administration class used to install PEAR packages and maintain the
  36.  * installed package database.
  37.  *
  38.  * TODO:
  39.  *   - Check dependencies break on package uninstall (when no force given)
  40.  *   - add a guessInstallDest() method with the code from _installFile() and
  41.  *     use that method in Registry::_rebuildFileMap() & Command_Registry::doList(),
  42.  *     others..
  43.  *
  44.  * @since PHP 4.0.2
  45.  * @author Stig Bakken <ssb@php.net>
  46.  * @author Martin Jansen <mj@php.net>
  47.  * @author Greg Beaver <cellog@php.net>
  48.  */
  49. class PEAR_Installer extends PEAR_Downloader
  50. {
  51.     // {{{ properties
  52.  
  53.     /** name of the package directory, for example Foo-1.0
  54.      * @var string
  55.      */
  56.     var $pkgdir;
  57.  
  58.     /** directory where PHP code files go
  59.      * @var string
  60.      */
  61.     var $phpdir;
  62.  
  63.     /** directory where PHP extension files go
  64.      * @var string
  65.      */
  66.     var $extdir;
  67.  
  68.     /** directory where documentation goes
  69.      * @var string
  70.      */
  71.     var $docdir;
  72.  
  73.     /** installation root directory (ala PHP's INSTALL_ROOT or
  74.      * automake's DESTDIR
  75.      * @var string
  76.      */
  77.     var $installroot = '';
  78.  
  79.     /** debug level
  80.      * @var int
  81.      */
  82.     var $debug = 1;
  83.  
  84.     /** temporary directory
  85.      * @var string
  86.      */
  87.     var $tmpdir;
  88.  
  89.     /** PEAR_Registry object used by the installer
  90.      * @var object
  91.      */
  92.     var $registry;
  93.  
  94.     /** List of file transactions queued for an install/upgrade/uninstall.
  95.      *
  96.      *  Format:
  97.      *    array(
  98.      *      0 => array("rename => array("from-file", "to-file")),
  99.      *      1 => array("delete" => array("file-to-delete")),
  100.      *      ...
  101.      *    )
  102.      *
  103.      * @var array
  104.      */
  105.     var $file_operations = array();
  106.  
  107.     // }}}
  108.  
  109.     // {{{ constructor
  110.  
  111.     /**
  112.      * PEAR_Installer constructor.
  113.      *
  114.      * @param object $ui user interface object (instance of PEAR_Frontend_*)
  115.      *
  116.      * @access public
  117.      */
  118.     function PEAR_Installer(&$ui)
  119.     {
  120.         parent::PEAR_Common();
  121.         $this->setFrontendObject($ui);
  122.         $this->debug = $this->config->get('verbose');
  123.         //$this->registry = &new PEAR_Registry($this->config->get('php_dir'));
  124.     }
  125.  
  126.     // }}}
  127.  
  128.     // {{{ _deletePackageFiles()
  129.  
  130.     /**
  131.      * Delete a package's installed files, does not remove empty directories.
  132.      *
  133.      * @param string $package package name
  134.      *
  135.      * @return bool TRUE on success, or a PEAR error on failure
  136.      *
  137.      * @access private
  138.      */
  139.     function _deletePackageFiles($package)
  140.     {
  141.         if (!strlen($package)) {
  142.             return $this->raiseError("No package to uninstall given");
  143.         }
  144.         $filelist = $this->registry->packageInfo($package, 'filelist');
  145.         if ($filelist == null) {
  146.             return $this->raiseError("$package not installed");
  147.         }
  148.         foreach ($filelist as $file => $props) {
  149.             if (empty($props['installed_as'])) {
  150.                 continue;
  151.             }
  152.             $path = $this->_prependPath($props['installed_as'], $this->installroot);
  153.             $this->addFileOperation('delete', array($path));
  154.         }
  155.         return true;
  156.     }
  157.  
  158.     // }}}
  159.     // {{{ _installFile()
  160.  
  161.     /**
  162.      * @param string filename
  163.      * @param array attributes from <file> tag in package.xml
  164.      * @param string path to install the file in
  165.      * @param array options from command-line
  166.      * @access private
  167.      */
  168.     function _installFile($file, $atts, $tmp_path, $options)
  169.     {
  170.         // {{{ return if this file is meant for another platform
  171.         static $os;
  172.         if (isset($atts['platform'])) {
  173.             if (empty($os)) {
  174.                 include_once "OS/Guess.php";
  175.                 $os = new OS_Guess();
  176.             }
  177.             if (!$os->matchSignature($atts['platform'])) {
  178.                 $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
  179.                 return PEAR_INSTALLER_SKIPPED;
  180.             }
  181.         }
  182.         // }}}
  183.  
  184.         // {{{ assemble the destination paths
  185.         switch ($atts['role']) {
  186.             case 'doc':
  187.             case 'data':
  188.             case 'test':
  189.                 $dest_dir = $this->config->get($atts['role'] . '_dir') .
  190.                             DIRECTORY_SEPARATOR . $this->pkginfo['package'];
  191.                 unset($atts['baseinstalldir']);
  192.                 break;
  193.             case 'ext':
  194.             case 'php':
  195.                 $dest_dir = $this->config->get($atts['role'] . '_dir');
  196.                 break;
  197.             case 'script':
  198.                 $dest_dir = $this->config->get('bin_dir');
  199.                 break;
  200.             case 'src':
  201.             case 'extsrc':
  202.                 $this->source_files++;
  203.                 return;
  204.             default:
  205.                 return $this->raiseError("Invalid role `$atts[role]' for file $file");
  206.         }
  207.         $save_destdir = $dest_dir;
  208.         if (!empty($atts['baseinstalldir'])) {
  209.             $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
  210.         }
  211.         if (dirname($file) != '.' && empty($atts['install-as'])) {
  212.             $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
  213.         }
  214.         if (empty($atts['install-as'])) {
  215.             $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
  216.         } else {
  217.             $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
  218.         }
  219.         $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
  220.  
  221.         // Clean up the DIRECTORY_SEPARATOR mess
  222.         $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
  223.         list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
  224.                                                     DIRECTORY_SEPARATOR,
  225.                                                     array($dest_file, $orig_file));
  226.         $installed_as = $dest_file;
  227.         $final_dest_file = $this->_prependPath($dest_file, $this->installroot);
  228.         $dest_dir = dirname($final_dest_file);
  229.         $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  230.         // }}}
  231.  
  232.         if (!@is_dir($dest_dir)) {
  233.             if (!$this->mkDirHier($dest_dir)) {
  234.                 return $this->raiseError("failed to mkdir $dest_dir",
  235.                                          PEAR_INSTALLER_FAILED);
  236.             }
  237.             $this->log(3, "+ mkdir $dest_dir");
  238.         }
  239.         if (empty($atts['replacements'])) {
  240.             if (!file_exists($orig_file)) {
  241.                 return $this->raiseError("file does not exist",
  242.                                          PEAR_INSTALLER_FAILED);
  243.             }
  244.             if (!@copy($orig_file, $dest_file)) {
  245.                 return $this->raiseError("failed to write $dest_file",
  246.                                          PEAR_INSTALLER_FAILED);
  247.             }
  248.             $this->log(3, "+ cp $orig_file $dest_file");
  249.             if (isset($atts['md5sum'])) {
  250.                 $md5sum = md5_file($dest_file);
  251.             }
  252.         } else {
  253.             // {{{ file with replacements
  254.             if (!file_exists($orig_file)) {
  255.                 return $this->raiseError("file does not exist",
  256.                                          PEAR_INSTALLER_FAILED);
  257.             }
  258.             $fp = fopen($orig_file, "r");
  259.             $contents = fread($fp, filesize($orig_file));
  260.             fclose($fp);
  261.             if (isset($atts['md5sum'])) {
  262.                 $md5sum = md5($contents);
  263.             }
  264.             $subst_from = $subst_to = array();
  265.             foreach ($atts['replacements'] as $a) {
  266.                 $to = '';
  267.                 if ($a['type'] == 'php-const') {
  268.                     if (preg_match('/^[a-z0-9_]+$/i', $a['to'])) {
  269.                         eval("\$to = $a[to];");
  270.                     } else {
  271.                         $this->log(0, "invalid php-const replacement: $a[to]");
  272.                         continue;
  273.                     }
  274.                 } elseif ($a['type'] == 'pear-config') {
  275.                     $to = $this->config->get($a['to']);
  276.                     if (is_null($to)) {
  277.                         $this->log(0, "invalid pear-config replacement: $a[to]");
  278.                         continue;
  279.                     }
  280.                 } elseif ($a['type'] == 'package-info') {
  281.                     if (isset($this->pkginfo[$a['to']]) && is_string($this->pkginfo[$a['to']])) {
  282.                         $to = $this->pkginfo[$a['to']];
  283.                     } else {
  284.                         $this->log(0, "invalid package-info replacement: $a[to]");
  285.                         continue;
  286.                     }
  287.                 }
  288.                 if (!is_null($to)) {
  289.                     $subst_from[] = $a['from'];
  290.                     $subst_to[] = $to;
  291.                 }
  292.             }
  293.             $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
  294.             if (sizeof($subst_from)) {
  295.                 $contents = str_replace($subst_from, $subst_to, $contents);
  296.             }
  297.             $wp = @fopen($dest_file, "wb");
  298.             if (!is_resource($wp)) {
  299.                 return $this->raiseError("failed to create $dest_file: $php_errormsg",
  300.                                          PEAR_INSTALLER_FAILED);
  301.             }
  302.             if (!fwrite($wp, $contents)) {
  303.                 return $this->raiseError("failed writing to $dest_file: $php_errormsg",
  304.                                          PEAR_INSTALLER_FAILED);
  305.             }
  306.             fclose($wp);
  307.             // }}}
  308.         }
  309.         // {{{ check the md5
  310.         if (isset($md5sum)) {
  311.             if (strtolower($md5sum) == strtolower($atts['md5sum'])) {
  312.                 $this->log(2, "md5sum ok: $final_dest_file");
  313.             } else {
  314.                 if (empty($options['force'])) {
  315.                     // delete the file
  316.                     @unlink($dest_file);
  317.                     return $this->raiseError("bad md5sum for file $final_dest_file",
  318.                                              PEAR_INSTALLER_FAILED);
  319.                 } else {
  320.                     $this->log(0, "warning : bad md5sum for file $final_dest_file");
  321.                 }
  322.             }
  323.         }
  324.         // }}}
  325.         // {{{ set file permissions
  326.         if (!OS_WINDOWS) {
  327.             if ($atts['role'] == 'script') {
  328.                 $mode = 0777 & ~(int)octdec($this->config->get('umask'));
  329.                 $this->log(3, "+ chmod +x $dest_file");
  330.             } else {
  331.                 $mode = 0666 & ~(int)octdec($this->config->get('umask'));
  332.             }
  333.             $this->addFileOperation("chmod", array($mode, $dest_file));
  334.             if (!@chmod($dest_file, $mode)) {
  335.                 $this->log(0, "failed to change mode of $dest_file");
  336.             }
  337.         }
  338.         // }}}
  339.         $this->addFileOperation("rename", array($dest_file, $final_dest_file));
  340.         // Store the full path where the file was installed for easy unistall
  341.         $this->addFileOperation("installed_as", array($file, $installed_as,
  342.                                 $save_destdir, dirname(substr($dest_file, strlen($save_destdir)))));
  343.  
  344.         //$this->log(2, "installed: $dest_file");
  345.         return PEAR_INSTALLER_OK;
  346.     }
  347.  
  348.     // }}}
  349.     // {{{ addFileOperation()
  350.  
  351.     /**
  352.      * Add a file operation to the current file transaction.
  353.      *
  354.      * @see startFileTransaction()
  355.      * @var string $type This can be one of:
  356.      *    - rename:  rename a file ($data has 2 values)
  357.      *    - chmod:   change permissions on a file ($data has 2 values)
  358.      *    - delete:  delete a file ($data has 1 value)
  359.      *    - rmdir:   delete a directory if empty ($data has 1 value)
  360.      *    - installed_as: mark a file as installed ($data has 4 values).
  361.      * @var array $data For all file operations, this array must contain the
  362.      *    full path to the file or directory that is being operated on.  For
  363.      *    the rename command, the first parameter must be the file to rename,
  364.      *    the second its new name.
  365.      *
  366.      *    The installed_as operation contains 4 elements in this order:
  367.      *    1. Filename as listed in the filelist element from package.xml
  368.      *    2. Full path to the installed file
  369.      *    3. Full path from the php_dir configuration variable used in this
  370.      *       installation
  371.      *    4. Relative path from the php_dir that this file is installed in
  372.      */
  373.     function addFileOperation($type, $data)
  374.     {
  375.         if (!is_array($data)) {
  376.             return $this->raiseError('Internal Error: $data in addFileOperation'
  377.                 . ' must be an array, was ' . gettype($data));
  378.         }
  379.         if ($type == 'chmod') {
  380.             $octmode = decoct($data[0]);
  381.             $this->log(3, "adding to transaction: $type $octmode $data[1]");
  382.         } else {
  383.             $this->log(3, "adding to transaction: $type " . implode(" ", $data));
  384.         }
  385.         $this->file_operations[] = array($type, $data);
  386.     }
  387.  
  388.     // }}}
  389.     // {{{ startFileTransaction()
  390.  
  391.     function startFileTransaction($rollback_in_case = false)
  392.     {
  393.         if (count($this->file_operations) && $rollback_in_case) {
  394.             $this->rollbackFileTransaction();
  395.         }
  396.         $this->file_operations = array();
  397.     }
  398.  
  399.     // }}}
  400.     // {{{ commitFileTransaction()
  401.  
  402.     function commitFileTransaction()
  403.     {
  404.         $n = count($this->file_operations);
  405.         $this->log(2, "about to commit $n file operations");
  406.         // {{{ first, check permissions and such manually
  407.         $errors = array();
  408.         foreach ($this->file_operations as $tr) {
  409.             list($type, $data) = $tr;
  410.             switch ($type) {
  411.                 case 'rename':
  412.                     if (!file_exists($data[0])) {
  413.                         $errors[] = "cannot rename file $data[0], doesn't exist";
  414.                     }
  415.                     // check that dest dir. is writable
  416.                     if (!is_writable(dirname($data[1]))) {
  417.                         $errors[] = "permission denied ($type): $data[1]";
  418.                     }
  419.                     break;
  420.                 case 'chmod':
  421.                     // check that file is writable
  422.                     if (!is_writable($data[1])) {
  423.                         $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
  424.                     }
  425.                     break;
  426.                 case 'delete':
  427.                     if (!file_exists($data[0])) {
  428.                         $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
  429.                     }
  430.                     // check that directory is writable
  431.                     if (file_exists($data[0]) && !is_writable(dirname($data[0]))) {
  432.                         $errors[] = "permission denied ($type): $data[0]";
  433.                     }
  434.                     break;
  435.             }
  436.  
  437.         }
  438.         // }}}
  439.         $m = sizeof($errors);
  440.         if ($m > 0) {
  441.             foreach ($errors as $error) {
  442.                 $this->log(1, $error);
  443.             }
  444.             return false;
  445.         }
  446.         // {{{ really commit the transaction
  447.         foreach ($this->file_operations as $tr) {
  448.             list($type, $data) = $tr;
  449.             switch ($type) {
  450.                 case 'rename':
  451.                     @unlink($data[1]);
  452.                     @rename($data[0], $data[1]);
  453.                     $this->log(3, "+ mv $data[0] $data[1]");
  454.                     break;
  455.                 case 'chmod':
  456.                     @chmod($data[1], $data[0]);
  457.                     $octmode = decoct($data[0]);
  458.                     $this->log(3, "+ chmod $octmode $data[1]");
  459.                     break;
  460.                 case 'delete':
  461.                     @unlink($data[0]);
  462.                     $this->log(3, "+ rm $data[0]");
  463.                     break;
  464.                 case 'rmdir':
  465.                     @rmdir($data[0]);
  466.                     $this->log(3, "+ rmdir $data[0]");
  467.                     break;
  468.                 case 'installed_as':
  469.                     $this->pkginfo['filelist'][$data[0]]['installed_as'] = $data[1];
  470.                     if (!isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) {
  471.                         $this->pkginfo['filelist']['dirtree'][dirname($data[1])] = true;
  472.                         while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
  473.                               && $data[3] != '.') {
  474.                             $this->pkginfo['filelist']['dirtree']
  475.                                 [$this->_prependPath($data[3], $data[2])] = true;
  476.                             $data[3] = dirname($data[3]);
  477.                         }
  478.                     }
  479.                     break;
  480.             }
  481.         }
  482.         // }}}
  483.         $this->log(2, "successfully committed $n file operations");
  484.         $this->file_operations = array();
  485.         return true;
  486.     }
  487.  
  488.     // }}}
  489.     // {{{ rollbackFileTransaction()
  490.  
  491.     function rollbackFileTransaction()
  492.     {
  493.         $n = count($this->file_operations);
  494.         $this->log(2, "rolling back $n file operations");
  495.         foreach ($this->file_operations as $tr) {
  496.             list($type, $data) = $tr;
  497.             switch ($type) {
  498.                 case 'rename':
  499.                     @unlink($data[0]);
  500.                     $this->log(3, "+ rm $data[0]");
  501.                     break;
  502.                 case 'mkdir':
  503.                     @rmdir($data[0]);
  504.                     $this->log(3, "+ rmdir $data[0]");
  505.                     break;
  506.                 case 'chmod':
  507.                     break;
  508.                 case 'delete':
  509.                     break;
  510.                 case 'installed_as':
  511.                     if (isset($this->pkginfo['filelist'])) {
  512.                         unset($this->pkginfo['filelist'][$data[0]]['installed_as']);
  513.                     }
  514.                     if (isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) {
  515.                         unset($this->pkginfo['filelist']['dirtree'][dirname($data[1])]);
  516.                         while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
  517.                               && $data[3] != '.') {
  518.                             unset($this->pkginfo['filelist']['dirtree']
  519.                                 [$this->_prependPath($data[3], $data[2])]);
  520.                             $data[3] = dirname($data[3]);
  521.                         }
  522.                     }
  523.                     if (isset($this->pkginfo['filelist']['dirtree'])
  524.                           && !count($this->pkginfo['filelist']['dirtree'])) {
  525.                         unset($this->pkginfo['filelist']['dirtree']);
  526.                     }
  527.                     break;
  528.             }
  529.         }
  530.         $this->file_operations = array();
  531.     }
  532.  
  533.     // }}}
  534.     // {{{ mkDirHier($dir)
  535.  
  536.     function mkDirHier($dir)
  537.     {
  538.         $this->addFileOperation('mkdir', array($dir));
  539.         return parent::mkDirHier($dir);
  540.     }
  541.  
  542.     // }}}
  543.     // {{{ download()
  544.  
  545.     /**
  546.      * Download any files and their dependencies, if necessary
  547.      *
  548.      * @param array a mixed list of package names, local files, or package.xml
  549.      * @param PEAR_Config
  550.      * @param array options from the command line
  551.      * @param array this is the array that will be populated with packages to
  552.      *              install.  Format of each entry:
  553.      *
  554.      * <code>
  555.      * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  556.      *    'info' => array() // parsed package.xml
  557.      * );
  558.      * </code>
  559.      * @param array this will be populated with any error messages
  560.      * @param false private recursion variable
  561.      * @param false private recursion variable
  562.      * @param false private recursion variable
  563.      * @deprecated in favor of PEAR_Downloader
  564.      */
  565.     function download($packages, $options, &$config, &$installpackages,
  566.                       &$errors, $installed = false, $willinstall = false, $state = false)
  567.     {
  568.         // trickiness: initialize here
  569.         parent::PEAR_Downloader($this->ui, $options, $config);
  570.         $ret = parent::download($packages);
  571.         $errors = $this->getErrorMsgs();
  572.         $installpackages = $this->getDownloadedPackages();
  573.         trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
  574.                       "in favor of PEAR_Downloader class", E_USER_WARNING);
  575.         return $ret;
  576.     }
  577.  
  578.     // }}}
  579.     // {{{ install()
  580.  
  581.     /**
  582.      * Installs the files within the package file specified.
  583.      *
  584.      * @param string $pkgfile path to the package file
  585.      * @param array $options
  586.      * recognized options:
  587.      * - installroot   : optional prefix directory for installation
  588.      * - force         : force installation
  589.      * - register-only : update registry but don't install files
  590.      * - upgrade       : upgrade existing install
  591.      * - soft          : fail silently
  592.      * - nodeps        : ignore dependency conflicts/missing dependencies
  593.      * - alldeps       : install all dependencies
  594.      * - onlyreqdeps   : install only required dependencies
  595.      *
  596.      * @return array|PEAR_Error package info if successful
  597.      */
  598.  
  599.     function install($pkgfile, $options = array())
  600.     {
  601.         $php_dir = $this->config->get('php_dir');
  602.         if (isset($options['installroot'])) {
  603.             if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
  604.                 $options['installroot'] = substr($options['installroot'], 0, -1);
  605.             }
  606.             $php_dir = $this->_prependPath($php_dir, $options['installroot']);
  607.             $this->installroot = $options['installroot'];
  608.         } else {
  609.             $this->installroot = '';
  610.         }
  611.         $this->registry = &new PEAR_Registry($php_dir);
  612.         //  ==> XXX should be removed later on
  613.         $flag_old_format = false;
  614.  
  615.         if (substr($pkgfile, -4) == '.xml') {
  616.             $descfile = $pkgfile;
  617.         } else {
  618.             // {{{ Decompress pack in tmp dir -------------------------------------
  619.  
  620.             // To allow relative package file names
  621.             $pkgfile = realpath($pkgfile);
  622.  
  623.             if (PEAR::isError($tmpdir = System::mktemp('-d'))) {
  624.                 return $tmpdir;
  625.             }
  626.             $this->log(3, '+ tmp dir created at ' . $tmpdir);
  627.  
  628.             $tar = new Archive_Tar($pkgfile);
  629.             if (!@$tar->extract($tmpdir)) {
  630.                 return $this->raiseError("unable to unpack $pkgfile");
  631.             }
  632.  
  633.             // {{{ Look for existing package file
  634.             $descfile = $tmpdir . DIRECTORY_SEPARATOR . 'package.xml';
  635.  
  636.             if (!is_file($descfile)) {
  637.                 // ----- Look for old package archive format
  638.                 // In this format the package.xml file was inside the
  639.                 // Package-n.n directory
  640.                 $dp = opendir($tmpdir);
  641.                 do {
  642.                     $pkgdir = readdir($dp);
  643.                 } while ($pkgdir{0} == '.');
  644.  
  645.                 $descfile = $tmpdir . DIRECTORY_SEPARATOR . $pkgdir . DIRECTORY_SEPARATOR . 'package.xml';
  646.                 $flag_old_format = true;
  647.                 $this->log(0, "warning : you are using an archive with an old format");
  648.             }
  649.             // }}}
  650.             // <== XXX This part should be removed later on
  651.             // }}}
  652.         }
  653.  
  654.         if (!is_file($descfile)) {
  655.             return $this->raiseError("no package.xml file after extracting the archive");
  656.         }
  657.  
  658.         // Parse xml file -----------------------------------------------
  659.         $pkginfo = $this->infoFromDescriptionFile($descfile);
  660.         if (PEAR::isError($pkginfo)) {
  661.             return $pkginfo;
  662.         }
  663.         $this->validatePackageInfo($pkginfo, $errors, $warnings);
  664.         // XXX We allow warnings, do we have to do it?
  665.         if (count($errors)) {
  666.             if (empty($options['force'])) {
  667.                 return $this->raiseError("The following errors where found (use force option to install anyway):\n".
  668.                                          implode("\n", $errors));
  669.             } else {
  670.                 $this->log(0, "warning : the following errors were found:\n".
  671.                            implode("\n", $errors));
  672.             }
  673.         }
  674.  
  675.         $pkgname = $pkginfo['package'];
  676.  
  677.         // {{{ Check dependencies -------------------------------------------
  678.         if (isset($pkginfo['release_deps']) && empty($options['nodeps'])) {
  679.             $dep_errors = '';
  680.             $error = $this->checkDeps($pkginfo, $dep_errors);
  681.             if ($error == true) {
  682.                 if (empty($options['soft'])) {
  683.                     $this->log(0, substr($dep_errors, 1));
  684.                 }
  685.                 return $this->raiseError("$pkgname: Dependencies failed");
  686.             } else if (!empty($dep_errors)) {
  687.                 // Print optional dependencies
  688.                 if (empty($options['soft'])) {
  689.                     $this->log(0, $dep_errors);
  690.                 }
  691.             }
  692.         }
  693.         // }}}
  694.  
  695.         // {{{ checks to do when not in "force" mode
  696.         if (empty($options['force'])) {
  697.             $test = $this->registry->checkFileMap($pkginfo);
  698.             if (sizeof($test)) {
  699.                 $tmp = $test;
  700.                 foreach ($tmp as $file => $pkg) {
  701.                     if ($pkg == $pkgname) {
  702.                         unset($test[$file]);
  703.                     }
  704.                 }
  705.                 if (sizeof($test)) {
  706.                     $msg = "$pkgname: conflicting files found:\n";
  707.                     $longest = max(array_map("strlen", array_keys($test)));
  708.                     $fmt = "%${longest}s (%s)\n";
  709.                     foreach ($test as $file => $pkg) {
  710.                         $msg .= sprintf($fmt, $file, $pkg);
  711.                     }
  712.                     return $this->raiseError($msg);
  713.                 }
  714.             }
  715.         }
  716.         // }}}
  717.  
  718.         $this->startFileTransaction();
  719.  
  720.         if (empty($options['upgrade'])) {
  721.             // checks to do only when installing new packages
  722.             if (empty($options['force']) && $this->registry->packageExists($pkgname)) {
  723.                 return $this->raiseError("$pkgname already installed");
  724.             }
  725.         } else {
  726.             if ($this->registry->packageExists($pkgname)) {
  727.                 $v1 = $this->registry->packageInfo($pkgname, 'version');
  728.                 $v2 = $pkginfo['version'];
  729.                 $cmp = version_compare("$v1", "$v2", 'gt');
  730.                 if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
  731.                     return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
  732.                 }
  733.                 if (empty($options['register-only'])) {
  734.                     // when upgrading, remove old release's files first:
  735.                     if (PEAR::isError($err = $this->_deletePackageFiles($pkgname))) {
  736.                         return $this->raiseError($err);
  737.                     }
  738.                 }
  739.             }
  740.         }
  741.  
  742.         // {{{ Copy files to dest dir ---------------------------------------
  743.  
  744.         // info from the package it self we want to access from _installFile
  745.         $this->pkginfo = &$pkginfo;
  746.         // used to determine whether we should build any C code
  747.         $this->source_files = 0;
  748.  
  749.         if (empty($options['register-only'])) {
  750.             if (!is_dir($php_dir)) {
  751.                 return $this->raiseError("no script destination directory\n",
  752.                                          null, PEAR_ERROR_DIE);
  753.             }
  754.  
  755.             $tmp_path = dirname($descfile);
  756.             if (substr($pkgfile, -4) != '.xml') {
  757.                 $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkginfo['version'];
  758.             }
  759.  
  760.             //  ==> XXX This part should be removed later on
  761.             if ($flag_old_format) {
  762.                 $tmp_path = dirname($descfile);
  763.             }
  764.             // <== XXX This part should be removed later on
  765.  
  766.             // {{{ install files
  767.             foreach ($pkginfo['filelist'] as $file => $atts) {
  768.                 $this->expectError(PEAR_INSTALLER_FAILED);
  769.                 $res = $this->_installFile($file, $atts, $tmp_path, $options);
  770.                 $this->popExpect();
  771.                 if (PEAR::isError($res)) {
  772.                     if (empty($options['ignore-errors'])) {
  773.                         $this->rollbackFileTransaction();
  774.                         if ($res->getMessage() == "file does not exist") {
  775.                             $this->raiseError("file $file in package.xml does not exist");
  776.                         }
  777.                         return $this->raiseError($res);
  778.                     } else {
  779.                         $this->log(0, "Warning: " . $res->getMessage());
  780.                     }
  781.                 }
  782.                 if ($res != PEAR_INSTALLER_OK) {
  783.                     // Do not register files that were not installed
  784.                     unset($pkginfo['filelist'][$file]);
  785.                 }
  786.             }
  787.             // }}}
  788.  
  789.             // {{{ compile and install source files
  790.             if ($this->source_files > 0 && empty($options['nobuild'])) {
  791.                 $this->log(1, "$this->source_files source files, building");
  792.                 $bob = &new PEAR_Builder($this->ui);
  793.                 $bob->debug = $this->debug;
  794.                 $built = $bob->build($descfile, array(&$this, '_buildCallback'));
  795.                 if (PEAR::isError($built)) {
  796.                     $this->rollbackFileTransaction();
  797.                     return $built;
  798.                 }
  799.                 $this->log(1, "\nBuild process completed successfully");
  800.                 foreach ($built as $ext) {
  801.                     $bn = basename($ext['file']);
  802.                     list($_ext_name, ) = explode('.', $bn);
  803.                     if (extension_loaded($_ext_name)) {
  804.                         $this->raiseError("Extension '$_ext_name' already loaded. Please unload it ".
  805.                                           "in your php.ini file prior to install or upgrade it.");
  806.                     }
  807.                     $dest = $this->config->get('ext_dir') . DIRECTORY_SEPARATOR . $bn;
  808.                     $this->log(1, "Installing '$bn' at ext_dir ($dest)");
  809.                     $this->log(3, "+ cp $ext[file] ext_dir ($dest)");
  810.                     $copyto = $this->_prependPath($dest, $this->installroot);
  811.                     if (!@copy($ext['file'], $copyto)) {
  812.                         $this->rollbackFileTransaction();
  813.                         return $this->raiseError("failed to copy $bn to $copyto");
  814.                     }
  815.                     $pkginfo['filelist'][$bn] = array(
  816.                         'role' => 'ext',
  817.                         'installed_as' => $dest,
  818.                         'php_api' => $ext['php_api'],
  819.                         'zend_mod_api' => $ext['zend_mod_api'],
  820.                         'zend_ext_api' => $ext['zend_ext_api'],
  821.                         );
  822.                 }
  823.             }
  824.             // }}}
  825.         }
  826.  
  827.         if (!$this->commitFileTransaction()) {
  828.             $this->rollbackFileTransaction();
  829.             return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
  830.         }
  831.         // }}}
  832.  
  833.         $ret = false;
  834.         // {{{ Register that the package is installed -----------------------
  835.         if (empty($options['upgrade'])) {
  836.             // if 'force' is used, replace the info in registry
  837.             if (!empty($options['force']) && $this->registry->packageExists($pkgname)) {
  838.                 $this->registry->deletePackage($pkgname);
  839.             }
  840.             $ret = $this->registry->addPackage($pkgname, $pkginfo);
  841.         } else {
  842.             // new: upgrade installs a package if it isn't installed
  843.             if (!$this->registry->packageExists($pkgname)) {
  844.                 $ret = $this->registry->addPackage($pkgname, $pkginfo);
  845.             } else {
  846.                 $ret = $this->registry->updatePackage($pkgname, $pkginfo, false);
  847.             }
  848.         }
  849.         if (!$ret) {
  850.             return $this->raiseError("Adding package $pkgname to registry failed");
  851.         }
  852.         // }}}
  853.         return $pkginfo;
  854.     }
  855.  
  856.     // }}}
  857.     // {{{ uninstall()
  858.  
  859.     /**
  860.      * Uninstall a package
  861.      *
  862.      * This method removes all files installed by the application, and then
  863.      * removes any empty directories.
  864.      * @param string package name
  865.      * @param array Command-line options.  Possibilities include:
  866.      *
  867.      *              - installroot: base installation dir, if not the default
  868.      *              - nodeps: do not process dependencies of other packages to ensure
  869.      *                        uninstallation does not break things
  870.      */
  871.     function uninstall($package, $options = array())
  872.     {
  873.         $php_dir = $this->config->get('php_dir');
  874.         if (isset($options['installroot'])) {
  875.             if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
  876.                 $options['installroot'] = substr($options['installroot'], 0, -1);
  877.             }
  878.             $this->installroot = $options['installroot'];
  879.             $php_dir = $this->_prependPath($php_dir, $this->installroot);
  880.         } else {
  881.             $this->installroot = '';
  882.         }
  883.         $this->registry = &new PEAR_Registry($php_dir);
  884.         $filelist = $this->registry->packageInfo($package, 'filelist');
  885.         if ($filelist == null) {
  886.             return $this->raiseError("$package not installed");
  887.         }
  888.         if (empty($options['nodeps'])) {
  889.             $depchecker = &new PEAR_Dependency($this->registry);
  890.             $error = $depchecker->checkPackageUninstall($errors, $warning, $package);
  891.             if ($error) {
  892.                 return $this->raiseError($errors . 'uninstall failed');
  893.             }
  894.             if ($warning) {
  895.                 $this->log(0, $warning);
  896.             }
  897.         }
  898.         // {{{ Delete the files
  899.         $this->startFileTransaction();
  900.         if (PEAR::isError($err = $this->_deletePackageFiles($package))) {
  901.             $this->rollbackFileTransaction();
  902.             return $this->raiseError($err);
  903.         }
  904.         if (!$this->commitFileTransaction()) {
  905.             $this->rollbackFileTransaction();
  906.             return $this->raiseError("uninstall failed");
  907.         } else {
  908.             $this->startFileTransaction();
  909.             if (!isset($filelist['dirtree']) || !count($filelist['dirtree'])) {
  910.                 return $this->registry->deletePackage($package);
  911.             }
  912.             // attempt to delete empty directories
  913.             uksort($filelist['dirtree'], array($this, '_sortDirs'));
  914.             foreach($filelist['dirtree'] as $dir => $notused) {
  915.                 $this->addFileOperation('rmdir', array($dir));
  916.             }
  917.             if (!$this->commitFileTransaction()) {
  918.                 $this->rollbackFileTransaction();
  919.             }
  920.         }
  921.         // }}}
  922.  
  923.         // Register that the package is no longer installed
  924.         return $this->registry->deletePackage($package);
  925.     }
  926.  
  927.     // }}}
  928.     // {{{ _sortDirs()
  929.     function _sortDirs($a, $b)
  930.     {
  931.         if (strnatcmp($a, $b) == -1) return 1;
  932.         if (strnatcmp($a, $b) == 1) return -1;
  933.         return 0;
  934.     }
  935.  
  936.     // }}}
  937.     // {{{ checkDeps()
  938.  
  939.     /**
  940.      * Check if the package meets all dependencies
  941.      *
  942.      * @param  array   Package information (passed by reference)
  943.      * @param  string  Error message (passed by reference)
  944.      * @return boolean False when no error occured, otherwise true
  945.      */
  946.     function checkDeps(&$pkginfo, &$errors)
  947.     {
  948.         if (empty($this->registry)) {
  949.             $this->registry = &new PEAR_Registry($this->config->get('php_dir'));
  950.         }
  951.         $depchecker = &new PEAR_Dependency($this->registry);
  952.         $error = $errors = '';
  953.         $failed_deps = $optional_deps = array();
  954.         if (is_array($pkginfo['release_deps'])) {
  955.             foreach($pkginfo['release_deps'] as $dep) {
  956.                 $code = $depchecker->callCheckMethod($error, $dep);
  957.                 if ($code) {
  958.                     if (isset($dep['optional']) && $dep['optional'] == 'yes') {
  959.                         $optional_deps[] = array($dep, $code, $error);
  960.                     } else {
  961.                         $failed_deps[] = array($dep, $code, $error);
  962.                     }
  963.                 }
  964.             }
  965.             // {{{ failed dependencies
  966.             $n = count($failed_deps);
  967.             if ($n > 0) {
  968.                 for ($i = 0; $i < $n; $i++) {
  969.                     if (isset($failed_deps[$i]['type'])) {
  970.                         $type = $failed_deps[$i]['type'];
  971.                     } else {
  972.                         $type = 'pkg';
  973.                     }
  974.                     switch ($failed_deps[$i][1]) {
  975.                         case PEAR_DEPENDENCY_MISSING:
  976.                             if ($type == 'pkg') {
  977.                                 // install
  978.                             }
  979.                             $errors .= "\n" . $failed_deps[$i][2];
  980.                             break;
  981.                         case PEAR_DEPENDENCY_UPGRADE_MINOR:
  982.                             if ($type == 'pkg') {
  983.                                 // upgrade
  984.                             }
  985.                             $errors .= "\n" . $failed_deps[$i][2];
  986.                             break;
  987.                         default:
  988.                             $errors .= "\n" . $failed_deps[$i][2];
  989.                             break;
  990.                     }
  991.                 }
  992.                 return true;
  993.             }
  994.             // }}}
  995.  
  996.             // {{{ optional dependencies
  997.             $count_optional = count($optional_deps);
  998.             if ($count_optional > 0) {
  999.                 $errors = "Optional dependencies:";
  1000.  
  1001.                 for ($i = 0; $i < $count_optional; $i++) {
  1002.                     if (isset($optional_deps[$i]['type'])) {
  1003.                         $type = $optional_deps[$i]['type'];
  1004.                     } else {
  1005.                         $type = 'pkg';
  1006.                     }
  1007.                     switch ($optional_deps[$i][1]) {
  1008.                         case PEAR_DEPENDENCY_MISSING:
  1009.                         case PEAR_DEPENDENCY_UPGRADE_MINOR:
  1010.                         default:
  1011.                             $errors .= "\n" . $optional_deps[$i][2];
  1012.                             break;
  1013.                     }
  1014.                 }
  1015.                 return false;
  1016.             }
  1017.             // }}}
  1018.         }
  1019.         return false;
  1020.     }
  1021.  
  1022.     // }}}
  1023.     // {{{ _buildCallback()
  1024.  
  1025.     function _buildCallback($what, $data)
  1026.     {
  1027.         if (($what == 'cmdoutput' && $this->debug > 1) ||
  1028.             ($what == 'output' && $this->debug > 0)) {
  1029.             $this->ui->outputData(rtrim($data), 'build');
  1030.         }
  1031.     }
  1032.  
  1033.     // }}}
  1034. }
  1035.  
  1036. // {{{ md5_file() utility function
  1037. if (!function_exists("md5_file")) {
  1038.     function md5_file($filename) {
  1039.         $fp = fopen($filename, "r");
  1040.         if (!$fp) return null;
  1041.         $contents = fread($fp, filesize($filename));
  1042.         fclose($fp);
  1043.         return md5($contents);
  1044.     }
  1045. }
  1046. // }}}
  1047.  
  1048. ?>
  1049.